// Created by plusminus on 17:53:07 - 25.09.2008
package org.andnav.osm.views.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.andnav.osm.util.BoundingBoxE6;
import org.andnav.osm.views.util.constants.OpenStreetMapViewConstants;

import android.database.sqlite.SQLiteDatabase;

/**
 *
 * @author Nicolas Gramlich
 *
 */
public class Util implements OpenStreetMapViewConstants{
	// ===========================================================
	// Constants
	// ===========================================================

	// ===========================================================
	// Fields
	// ===========================================================

	// ===========================================================
	// Constructors
	// ===========================================================

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods from SuperClass/Interfaces
	// ===========================================================

	// ===========================================================
	// Methods
	// ===========================================================

	public static String readString(final InputStream in, final int size) throws IOException{
		byte b [] = new byte[size];

		int lenght = in.read(b);
		if(b[0] == 0)
			return "";
		else if(lenght > 0)
			return new String(b, 0, lenght);
		else
			return "";
	}

	public static int readInt(final InputStream in) throws IOException{
		int res = 0;
		byte b [] = new byte[4];

		if(in.read(b)>0)
			res = (((int)(b[0] & 0xFF)) << 24) +
			  + ((b[1] & 0xFF) << 16) +
			  + ((b[2] & 0xFF) << 8) +
			  + (b[3] & 0xFF);

		return res;
	}

	public static int[] getMapTileFromCoordinates(final int aLat, final int aLon, final int zoom, final int[] reuse, final int aProjection) {
		return getMapTileFromCoordinates(aLat / 1E6, aLon / 1E6, zoom, reuse, aProjection);
	}

	public static int[] getMapTileFromCoordinates(final double aLat, final double aLon, final int zoom, final int[] aUseAsReturnValue, final int aProjection) {
		final int[] out = (aUseAsReturnValue != null) ? aUseAsReturnValue : new int[2];

		if(aProjection == 1)
			out[MAPTILE_LATITUDE_INDEX] = (int) Math.floor((1 - Math.log(Math.tan(aLat * Math.PI / 180) + 1 / Math.cos(aLat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom));
		else {
			final double E2 = (double) aLat*Math.PI/180;
			final long sradiusa = 6378137;
			final long sradiusb = 6356752;
			final double J2 = (double) Math.sqrt(sradiusa*sradiusa-sradiusb*sradiusb)/sradiusa;
			final double M2 = (double) Math.log((1+Math.sin(E2))/(1-Math.sin(E2)))/2-J2*Math.log((1+J2*Math.sin(E2))/(1-J2*Math.sin(E2)))/2;
			final double B2 = (double)(1 << zoom);
			out[MAPTILE_LATITUDE_INDEX] = (int) Math.floor(B2/2-M2*B2/2/Math.PI);
		}

		out[MAPTILE_LONGITUDE_INDEX] = (int) Math.floor((aLon + 180) / 360 * (1 << zoom));

		return out;
	}

    public static int[] getMapTileFromCoordinates(final int aLat, final int aLon, final int zoom, final int[] reuse) {
        	return getMapTileFromCoordinates(aLat / 1E6, aLon / 1E6, zoom, reuse);
    }

	public static int[] getMapTileFromCoordinates(final double aLat, final double aLon, final int zoom, final int[] aUseAsReturnValue) {
	        final int[] out = (aUseAsReturnValue != null) ? aUseAsReturnValue : new int[2];
	
	        out[MAPTILE_LATITUDE_INDEX] = (int) Math.floor((1 - Math.log(Math.tan(aLat * Math.PI / 180) + 1 / Math.cos(aLat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom));
	        out[MAPTILE_LONGITUDE_INDEX] = (int) Math.floor((aLon + 180) / 360 * (1 << zoom));
	
	        return out;
	}
	// Conversion of a MapTile to a BoudingBox

	public static BoundingBoxE6 getBoundingBoxFromMapTile(final int[] aMapTile, final int zoom, final int aProjection) {
		final int y = aMapTile[MAPTILE_LATITUDE_INDEX];
		final int x = aMapTile[MAPTILE_LONGITUDE_INDEX];
		return new BoundingBoxE6(tile2lat(y, zoom, aProjection), tile2lon(x + 1, zoom), tile2lat(y + 1, zoom, aProjection), tile2lon(x, zoom));
	}
 
	public static BoundingBoxE6 getBoundingBoxFromMapTile(final int[] aMapTile, final int zoom) {
        final int y = aMapTile[MAPTILE_LATITUDE_INDEX];
        final int x = aMapTile[MAPTILE_LONGITUDE_INDEX];
        return new BoundingBoxE6(tile2lat(y, zoom), tile2lon(x + 1, zoom), tile2lat(y + 1, zoom), tile2lon(x, zoom));
	}
	private static double tile2lon(int x, int aZoom) {
		return (x / Math.pow(2.0, aZoom) * 360.0) - 180;
	}
    
	private static double tile2lat(int y, int aZoom) {
        final double n = Math.PI - ((2.0 * Math.PI * y) / Math.pow(2.0, aZoom));
        return 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
    }
	
	private static double tile2lat(int y, int aZoom, final int aProjection) {

		if (aProjection == 1) {
			final double n = Math.PI
					- ((2.0 * Math.PI * y) / Math.pow(2.0, aZoom));
			return 180.0 / Math.PI
					* Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
		} else {
			final double MerkElipsK = 0.0000001;
			final long sradiusa = 6378137;
			final long sradiusb = 6356752;
			final double FExct = (double) Math.sqrt(sradiusa * sradiusa
					- sradiusb * sradiusb)
					/ sradiusa;
			final int TilesAtZoom = 1 << aZoom;
			double result = (y - TilesAtZoom / 2)
					/ -(TilesAtZoom / (2 * Math.PI));
			result = (2 * Math.atan(Math.exp(result)) - Math.PI / 2) * 180
					/ Math.PI;
			double Zu = result / (180 / Math.PI);
			double yy = ((y) - TilesAtZoom / 2);

			double Zum1 = Zu;
			Zu = Math
					.asin(1
							- ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct
									* Math.sin(Zum1), FExct))
							/ (Math.exp((2 * yy)
									/ -(TilesAtZoom / (2 * Math.PI))) * Math
									.pow(1 + FExct * Math.sin(Zum1), FExct)));
			while (Math.abs(Zum1 - Zu) >= MerkElipsK) {
				Zum1 = Zu;
				Zu = Math
						.asin(1
								- ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct
										* Math.sin(Zum1), FExct))
								/ (Math.exp((2 * yy)
										/ -(TilesAtZoom / (2 * Math.PI))) * Math
										.pow(1 + FExct * Math.sin(Zum1), FExct)));
			}

			result = Zu * 180 / Math.PI;

			return result;
		}
	}

	public static int x2lon(int x, int aZoom, final int MAPTILE_SIZEPX) {
		int px = MAPTILE_SIZEPX * (1 << aZoom);
		if (x < 0)
			x = px + x;
		if (x > px)
			x = x - px;
		return (int) (1E6 * (((double)x / px * 360.0) - 180));
	}

	public static double y2lat(int y, int aZoom, final int MAPTILE_SIZEPX) {
		final int aProjection = 1;

		if (aProjection == 1) {
			final double n = Math.PI
					- ((2.0 * Math.PI * y) / MAPTILE_SIZEPX * Math.pow(2.0, aZoom));
			return 180.0 / Math.PI
					* Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
		} else {
			final double MerkElipsK = 0.0000001;
			final long sradiusa = 6378137;
			final long sradiusb = 6356752;
			final double FExct = (double) Math.sqrt(sradiusa * sradiusa
					- sradiusb * sradiusb)
					/ sradiusa;
			final int TilesAtZoom = 1 << aZoom;
			double result = (y - TilesAtZoom / 2)
					/ -(TilesAtZoom / (2 * Math.PI));
			result = (2 * Math.atan(Math.exp(result)) - Math.PI / 2) * 180
					/ Math.PI;
			double Zu = result / (180 / Math.PI);
			double yy = ((y) - TilesAtZoom / 2);

			double Zum1 = Zu;
			Zu = Math
					.asin(1
							- ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct
									* Math.sin(Zum1), FExct))
							/ (Math.exp((2 * yy)
									/ -(TilesAtZoom / (2 * Math.PI))) * Math
									.pow(1 + FExct * Math.sin(Zum1), FExct)));
			while (Math.abs(Zum1 - Zu) >= MerkElipsK) {
				Zum1 = Zu;
				Zu = Math
						.asin(1
								- ((1 + Math.sin(Zum1)) * Math.pow(1 - FExct
										* Math.sin(Zum1), FExct))
								/ (Math.exp((2 * yy)
										/ -(TilesAtZoom / (2 * Math.PI))) * Math
										.pow(1 + FExct * Math.sin(Zum1), FExct)));
			}

			result = Zu * 180 / Math.PI;

			return result;
		}
	}

	public static String FileName2ID(String name) {
		return name.replace(".", "_").replace(" ", "_").replace("-", "_").trim();
	}

	public static File getRMapsFolder(String aName, boolean aShowAlertToast) {
		File folder = new File("/sdcard/rmaps/" + aName);
		if(!folder.exists()){
			if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
				folder.mkdirs();
			}else if(aShowAlertToast){
				//Toast.makeText(new Application(), "SD card is not available", Toast.LENGTH_LONG);
			}
		}

		return folder;
	}

	public static File getKmlFolder(String aName, boolean aShowAlertToast) {
		File folder = new File("/sdcard/rmaps/" + aName);
		if(!folder.exists()){
			if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
				folder.mkdirs();
			}else if(aShowAlertToast){
				//Toast.makeText(new Application(), "SD card is not available", Toast.LENGTH_LONG);
			}
		}

		return folder;
	}
	
	public static SQLiteDatabase getPOIDatabase(boolean aShowAlertToast) {
		File folder = new File("/sdcard/rmaps/");
		if(!folder.exists()){
			if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
				folder.mkdirs();
			}else if(aShowAlertToast){
				//Toast.makeText(new Application(), "SD card is not available", Toast.LENGTH_LONG);
			}
		}
		return SQLiteDatabase.openDatabase("/sdcard/rmaps/Places.data", null,
				SQLiteDatabase.CREATE_IF_NECESSARY);
	}

	public static SQLiteDatabase getIndexDatabase(boolean aShowAlertToast) {
		File folder = new File("/sdcard/rmaps/");
		if(!folder.exists()){
			if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
				folder.mkdirs();
			}else if(aShowAlertToast){
				//Toast.makeText(new Application(), "SD card is not available", Toast.LENGTH_LONG);
			}
		}

		return SQLiteDatabase.openDatabase("/sdcard/rmaps/MapIndex.data", null,
				SQLiteDatabase.CREATE_IF_NECESSARY);
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}
